package service

import (
	"context"
	"errors"
	"fmt"
	"gin-luban-server/global"
	"gin-luban-server/model"
	"gin-luban-server/model/request"
	"gin-luban-server/utils"
	"github.com/bndr/gojenkins"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"strings"
	"time"
)


// 初始化jenkins实例
func jenkinsInit(jenkinsConfig model.SysBasicConfigure, ctx context.Context) (jenkinsClient *gojenkins.Jenkins, err error) {
	//fmt.Println("step 1",jenkinsConfig.BaseUrl, jenkinsConfig.BasicUser, jenkinsConfig.BasicPasswd)
	jenkinsClient, err = gojenkins.CreateJenkins(nil, jenkinsConfig.BaseUrl, jenkinsConfig.BasicUser, jenkinsConfig.BasicPasswd).Init(ctx)
	return
}


// 创建JOB
func CreateJenkinsJobFile(id float64)  (err error) {
	//1. 获取应用信息
	var appConfig model.DeployAppConfigure
	err = global.GVA_DB.Where("id = ? ",id).First(&appConfig).Error

	if err != nil {
		return errors.New("应用配置表查询失败！")
	}
	// 2. 通过所在IDC 获取jenkins地址信息
	var jenkinsConfig model.SysBasicConfigure
	err = global.GVA_DB.Where("purpose = ? AND server_idc = ?","jenkins",appConfig.ServerIdc).First(&jenkinsConfig).Error
	if err != nil {
		return errors.New("基础配置表查询失败！")
	}

    // 3. 获取jenkins模板内容
	jenkinsJobContent, err := utils.GetJenkinsXml(appConfig)
	if err != nil {
		global.GVA_LOG.Error("获取jenkins job模板内容失败", zap.Any("err", err))
		return  err
	}


	viewName :=  fmt.Sprintf("%s-%s", appConfig.DeployEnv, appConfig.ProjectCode)

	// 4.开始事务-写入数据
	tx := global.GVA_DB.Begin()
	// 4.1 JOB和状态写数据库 (成功\失败)
	status := "1"
	jenkinsParams := model.DeployAppJenkins{AppsJobName: appConfig.AppsJobName,JenkinsView: viewName,ServerIdc: appConfig.ServerIdc,JenkinsServer: jenkinsConfig.BaseUrl,JenkinsJobFiles:jenkinsJobContent,JenkinsJobStatus: status}

	if !errors.Is(tx.Where("apps_job_name = ?", jenkinsParams.AppsJobName).First(&model.DeployAppJenkins{}).Error, gorm.ErrRecordNotFound) {
		return errors.New("存在相同jenkins Job 名称")
	}
	err = tx.Create(&jenkinsParams).Error

	if err != nil {
		tx.Rollback()
		return err
	}

	appConfig.DeployStatus = "1"
	// 通过唯一ID进行更新
	err = tx.Where("id = ?", appConfig.ID).First(&model.DeployAppConfigure{}).Updates(&appConfig).Error

	if err != nil {
		tx.Rollback()
		return err
	}

	// 4.2 创建job (成功\失败)
	ctx := context.Background()
	jenkinsClient, err := jenkinsInit(jenkinsConfig, ctx)
	if  err != nil {
		global.GVA_LOG.Error("jenkins 初始化失败", zap.Any("err", err))
		return  err
	}
	err = utils.CreateJenkinsJob(jenkinsClient, viewName, appConfig.AppsJobName, jenkinsJobContent, ctx)
	if err != nil {
		tx.Rollback()
		return err
	}

	tx.Commit()
	// 4.3 事务提交/回滚

	return err
}





//@author: heyibo
//@function: UpdateJenkinsJobFile
//@description: 更新jenkinsJob
//@param: params request.ApplyJenkinsParams
//@return: err error
func UpdateJenkinsJobFile(id float64)  (err error) {
	// 1. 获取参数
	var appConfig model.DeployAppConfigure
	err = global.GVA_DB.Where("id = ? ",id).First(&appConfig).Error
	if err != nil {
		return err
	}
	// 2. 通过所在IDC 获取jenkins地址信息
	var jenkinsConfig model.SysBasicConfigure
	err = global.GVA_DB.Where("purpose = ? AND server_idc = ?","jenkins",appConfig.ServerIdc).First(&jenkinsConfig).Error
	if err != nil {
		return errors.New("基础配置表查询失败！")
	}

	// 3. 获取jenkins模板内容
	jenkinsJobContent, err := utils.GetJenkinsXml(appConfig)
	if err != nil {
		global.GVA_LOG.Error("获取jenkins job模板内容失败", zap.Any("err", err))
		return  err
	}
	viewName :=  fmt.Sprintf("%s-%s", appConfig.DeployEnv, appConfig.ProjectCode)

	// 4.开始事务-写入数据
	tx := global.GVA_DB.Begin()
	// 4.1 JOB和状态写数据库 (成功\失败)
	status := "1"
	jenkinsParams := model.DeployAppJenkins{AppsJobName: appConfig.AppsJobName,JenkinsView: viewName,ServerIdc: appConfig.ServerIdc,JenkinsServer: jenkinsConfig.BaseUrl,JenkinsJobFiles:jenkinsJobContent,JenkinsJobStatus: status}
	err = tx.Where("apps_job_name = ?",appConfig.AppsJobName).First(&model.DeployAppJenkins{}).Updates(&jenkinsParams).Error

	if err != nil {
		tx.Rollback()
		return err
	}

	appConfig.DeployStatus = "3"   // 3是已经同步状态
	// 通过唯一ID进行更新
	err = tx.Where("id = ?", appConfig.ID).First(&model.DeployAppConfigure{}).Updates(&appConfig).Error

	if err != nil {
		tx.Rollback()
		return err
	}

	// 4.2 创建job (成功\失败)
	//ctx := context.Background()
	//jenkinsClient, err := jenkinsInit(jenkinsConfig, ctx)
	//if  err != nil {
	//	global.GVA_LOG.Error("jenkins 初始化失败", zap.Any("err", err))
	//	return  err
	//}

	// 不是用默认 第三方的 方法 自己写方法 缺点: 1. 默认类型application/xml 在这个版本jenkins无法更新  2. jenkins update方法没有返回error值
	err = utils.UpdateJenkinsJob(jenkinsConfig, appConfig, jenkinsJobContent)

	if err != nil {
		tx.Rollback()
		return err
	}

	tx.Commit()
	// 4.3 事务提交/回滚

	return err
}

//@author: heyibo
//@function: getJenkinsBuildJob
//@description: 构建应用
//@param: params request.JenkinsJobBuildParams
//@return: err error
func StartJenkinsJobBuild(params request.JenkinsJobBuildParams, deployUsername string, deployNickName string) (buildNumber int64, err error)  {
	// 1. 通过job 参数获取信息
	var appConfig model.DeployAppConfigure
	err = global.GVA_DB.Where("apps_job_name = ?",params.AppsJobName).First(&appConfig).Error
	if err != nil {
		return 0, err
	}
	// 2. 通过所在IDC 获取jenkins地址信息
	var jenkinsConfig model.SysBasicConfigure
	err = global.GVA_DB.Where("purpose = ? AND server_idc = ?","jenkins",appConfig.ServerIdc).First(&jenkinsConfig).Error
	if err != nil {
		return 0, errors.New("基础配置表查询失败！")
	}
	// 2. 触发jenkins 启动构建
	ctx := context.Background()
	jenkinsClient, err := jenkinsInit(jenkinsConfig, ctx)

	if  err != nil {
		global.GVA_LOG.Error("jenkins 初始化失败", zap.Any("err", err))
		return  0 ,err
	}
	currentVersion := time.Now().Format("20060102150405")
	TagInfo := ""
	// 生产环境有推到ftp上标识的发布版本使用TAG标签  没有tag就使用当前时间
	if params.TagName != "" {
		currentVersion = params.TagName
		// 获取git repo相关tag信息
		err, repoInfoList := GetAppGitRepoInfo(appConfig.AppsName)
		if err != nil {
			global.GVA_LOG.Error("查询git失败 获取仓库信息失败")
			return 0, errors.New("查询git失败 获取仓库信息失败")
		}
		// 获取标签信息
		for _, tag := range  repoInfoList.TagList {
			if tag.TagName == params.TagName {
				TagInfo = "标签名字: " + tag.TagName + ";" + "标签信息: " + tag.TagMessage + ";" + "标签对应的提交ID: " + tag.TagCommitID + ";" + "标签提交信息: " + tag.TagCommitMessage
				break
			}
		}
	}

	//
	buildJobPara := map[string]string {
		"BRANCH": params.BranchName,
		"currentVersion": currentVersion,
		"ifRestart": fmt.Sprintf("%v", params.IfRestart), // 需要将bool转成字符串格式传入job接收
	}

	// 触发构建

	_, err = jenkinsClient.BuildJob(ctx, appConfig.AppsJobName, buildJobPara)
	jobBuildList, err := jenkinsClient.GetAllBuildIds(ctx, appConfig.AppsJobName)
	if len(jobBuildList) >= 1 {
		buildNumber = jobBuildList[0].Number + 1 	  // 构建最后一次 + 1 = 下一次构建的ID
	} else {
		buildNumber = 1
	}
	if err != nil {
		global.GVA_LOG.Error("触发构建失败", zap.Any("err", err.Error))
		return  0, err
	}

	// 3. 构建信息写入数据表 deploy_apply_builds
	var deployType string
	if params.IfRestart {
		deployType = "restart" // 只重启，不用构建代码
		currentVersion = "-"
	} else {
		deployType = "normal"  // 正常发布
	}
	buildInfo := model.DeployBuildHistory {
		AppsName: appConfig.AppsName,
		AppsJobName: params.AppsJobName,
		BranchName: params.BranchName,
		DeployEnv: appConfig.DeployEnv,
		BuildNumber: buildNumber,
		DeployStatus: "1",
		Version: currentVersion,   // 发布版本号
		TagName: params.TagName,
		TagInfo: TagInfo,
		DeployUsername: deployUsername,
		DeployNickName: deployNickName,
		DeployType:deployType,  // 部署类型
	}


	err = global.GVA_DB.Create(&buildInfo).Error
	if err != nil {
		return 0, err
	}
	return buildNumber, err

}

//@author: heyibo
//@function: getJenkinsBuildJobLogs
//@description: 构建应用
//@param: params request.JenkinsJobBuildParams
//@return: err error
func GetJenkinsBuildJobLogs(params request.JenkinsBuildJobLogsParams) (logContent string,err error)  {
	// 1. 通过job 参数获取信息
	var appConfig model.DeployAppConfigure
	err = global.GVA_DB.Where("apps_job_name = ?",params.AppsJobName).First(&appConfig).Error
	if err != nil {
		return "", err
	}
	// 2. 通过所在IDC 获取jenkins地址信息
	var jenkinsConfig model.SysBasicConfigure
	err = global.GVA_DB.Where("purpose = ? AND server_idc = ?","jenkins",appConfig.ServerIdc).First(&jenkinsConfig).Error
	if err != nil {
		return "", errors.New("基础配置表查询失败！")
	}
	// 2. 触发jenkins 启动构建
	ctx := context.Background()
	jenkinsClient, err := jenkinsInit(jenkinsConfig, ctx)
	if  err != nil {
		global.GVA_LOG.Error("jenkins 初始化失败", zap.Any("err", err))
		return  "", err
	}

	var JenkinsJobName string
	// 判断JOB 主要是区分回滚的JOB
	if params.DeployType == "rollback" {
		if appConfig.AppsType == "service" {
			JenkinsJobName = global.GVA_CONFIG.Jenkins.RollbackJavaSpringJob
		} else {
			JenkinsJobName = global.GVA_CONFIG.Jenkins.RollbackVuePortalJob
		}
	} else {
		// 正常发布
		JenkinsJobName = appConfig.AppsJobName
	}

	// 判断 job不在队列中 5分钟
	for i := 0; i < 30; i++ {
		queue, _ := jenkinsClient.GetQueue(ctx)
		taskList := queue.GetTasksForJob(JenkinsJobName)
		if len(taskList) >= 1 {
			global.GVA_LOG.Error("此Job有队列 等待部署中")
			return "此Job有队列 等待部署中", nil
		} else {
			break
		}
		time.Sleep(time.Second + 10)
	}

	build, err := jenkinsClient.GetBuild(ctx, JenkinsJobName, params.BuildNumber)
	if err != nil {
		global.GVA_LOG.Error("job GetBuild 失败", zap.Any("err", err))
		return "等待构建中.", err
	}
	contentLog, _ := build.GetConsoleOutputFromIndex(ctx, 0)
	// 判断大小
	//fmt.Println("日志长度....",len(contentLog.Content))
	return SubstrByByte(contentLog.Content, 25600), err
}

// 截断字符串
func SubstrByByte(str string, length int) string {
	bs := []byte(str)
	byteLength := len([]byte(str))
	if byteLength <= length + 3000{
		return str
	} else {
		// 前面3000个字符 + 省略字符 + 后面字符
		return string(bs[0:3000])+ "\nSkipping " + fmt.Sprintf("%d", byteLength-length-3000) +"字符...\n" + string(bs[byteLength-length:])
	}

}

//@author: heyibo
//@function: CreateJenkinsJobFile
//@description: 创建jenkinsJob
//@param: jenkins AppsJenkins
//@return: err error, jenkins model.AppsJenkins

func CreateDeployAppJenkins(jenkins model.DeployAppJenkins) (err error) {
	if !errors.Is(global.GVA_DB.Where("apps_job_name = ?", jenkins.AppsJobName).First(&model.DeployAppJenkins{}).Error, gorm.ErrRecordNotFound) {
		return errors.New("存在相同jenkins Job 名称")
	}
	err = global.GVA_DB.Create(&jenkins).Error
	return err
}

//@author: heyibo
//@function: UpdateAppsJenkins
//@description: 创建jenkinsJob
//@param: jenkins AppsJenkins
//@return: err error, jenkins model.AppsJenkins

func UpdateDeployAppJenkins(jenkins model.DeployAppJenkins) (err error) {
	err = global.GVA_DB.Where("id = ?", jenkins.ID).First(&model.DeployAppJenkins{}).Updates(&jenkins).Error
	return err
}

// 在jenkins中创建 回滚JOB
func CreateRollbackJenkinsJob() (err error) {
	// 遍历所有的 jenkins信息
	var jenkinsConfigList []model.SysBasicConfigure
	// 前后端的JOB名字
	rollbackJobList := []string{"rollback-vue-portal-app", "rollback-java-spring-app"}

	err = global.GVA_DB.Where("purpose = ? ","jenkins").Find(&jenkinsConfigList).Error
	if err != nil {
		return errors.New("查询jenkins失败！")
	}
	// 在jenkins中创建前后端回滚的JOB view为 "rollback"
	for _, jenkinsConfig := range jenkinsConfigList {
		ctx := context.Background()
		jenkinsClient, err := jenkinsInit(jenkinsConfig, ctx)
		fmt.Println(jenkinsConfig.ServerIdc, jenkinsConfig.BaseUrl, jenkinsConfig.BasicUser, jenkinsConfig.BasicPasswd)

		if  err != nil {
			global.GVA_LOG.Error("jenkins 初始化失败", zap.Any("err", err))
			return  err
		}
		// 创建jenkins JOB 的主要逻辑
		for _,rollbackJob := range rollbackJobList {
			time.Sleep(500 * time.Millisecond)
			filePath := fmt.Sprintf("resource/template/rollback/%s.xml", rollbackJob)
			jenkinsJobContent, err := utils.GetFileContent(filePath)
			if err != nil {
				return err
			}
			err = utils.CreateJenkinsJob(jenkinsClient, "rollback", rollbackJob, jenkinsJobContent, ctx)
			if err != nil {
				if strings.Contains(err.Error(), "job already exists with the name") {
					continue
				}
				return err
			}
		}
	}
	return err
}


// 回滚JOB构建
func RollbackJenkinsJobBuild(params request.JenkinsJobRollbackParams, deployUsername string, deployNickName string) (buildNumber int64, err error)  {
	// 1. 通过job 参数获取信息
	var appConfig model.DeployAppConfigure
	err = global.GVA_DB.Where("apps_job_name = ?",params.AppsJobName).First(&appConfig).Error
	if err != nil {
		return 0, err
	}
	// 2. 通过所在IDC 获取jenkins地址信息
	var jenkinsConfig model.SysBasicConfigure
	err = global.GVA_DB.Where("purpose = ? AND server_idc = ?","jenkins",appConfig.ServerIdc).First(&jenkinsConfig).Error
	if err != nil {
		return 0, errors.New("基础配置表查询失败！")
	}

	// 获取job的机器列表
	var virtualList []model.DeployAppVirtual
	global.GVA_DB.Where("apps_job_name = ?", appConfig.AppsJobName).Find(&virtualList)

	var virtualCodes []string
	var virtualIps []string   // IP列表
	for _, v := range virtualList {
		virtualCodes = append(virtualCodes,  v.VirtualCode)
	}
	var cmdbVirtualList  []model.CmdbServer
	global.GVA_DB.Where("virtual_code IN ?", virtualCodes).Find(&cmdbVirtualList)

	for _, v := range cmdbVirtualList {
		virtualIps = append(virtualIps,  v.Ipaddress)
	}

	hosts := strings.Join(virtualIps, ",")
	// 2. 触发jenkins 启动构建
	ctx := context.Background()
	jenkinsClient, err := jenkinsInit(jenkinsConfig, ctx)
	if  err != nil {
		global.GVA_LOG.Error("jenkins 初始化失败", zap.Any("err", err))
		return  0 ,err
	}
	// 参数 支持构建前端后端
	buildJobPara := map[string]string {
		"ProjectName": appConfig.ProjectCode,
		"AppsName": appConfig.AppsName,
		"PackageName": appConfig.PackageName,  // 后端需要
		"DeployEnv": appConfig.DeployEnv,
		"BuildPath": appConfig.BuildPath,   // 前端需要
		"DeployPath": appConfig.DeployPath,
		"DeployRun": appConfig.DeployRun,
		"Version": params.Version,
		"DeployHostList":hosts,
	}
	var jobName string
	if appConfig.AppsType == "service" {
		jobName = global.GVA_CONFIG.Jenkins.RollbackJavaSpringJob
	} else {
		jobName =  global.GVA_CONFIG.Jenkins.RollbackVuePortalJob
	}
	// 触发构建
	_, err = jenkinsClient.BuildJob(ctx, jobName, buildJobPara)
	jobBuildList, err := jenkinsClient.GetAllBuildIds(ctx, jobName)

	if len(jobBuildList) >= 1 {
		buildNumber = jobBuildList[0].Number + 1 	  // 构建最后一次 + 1 = 下一次构建的ID
	} else {
		buildNumber = 1
	}
	if err != nil {
		global.GVA_LOG.Error("触发构建失败", zap.Any("err", err.Error))
		return  0, err
	}

	// 3. 构建信息写入数据表 deploy_apply_builds

	buildInfo := model.DeployBuildHistory {
		AppsName: appConfig.AppsName,
		AppsJobName: params.AppsJobName,
		DeployEnv: appConfig.DeployEnv,
		BuildNumber: buildNumber,
		DeployStatus: "1",
		DeployType: "rollback",
		Version: params.Version,   // 发布版本号
		DeployUsername: deployUsername,
		DeployNickName: deployNickName,
	}

	err = global.GVA_DB.Create(&buildInfo).Error
	if err != nil {
		return 0, err
	}
	return buildNumber, err

}
